﻿namespace Jhong.OWINSession.InProcess
{
    using System;
    using System.Collections.Generic;

    internal class CircularCollection<TKey, TValue> : IDictionary<TKey, TValue>
    {
        private struct Entry
        {
            public int HashCode { get; set; }

            public int Next { get; set; }

            public TKey Key { get; set; }

            public TValue Value { get; set; }
        }

        public CircularCollection()
            : this(0)
        {
        }

        public CircularCollection(int capacity)
        {
            if (capacity > 0) this.Initialization(capacity);
        }

        private Entry[] _entrys;

        private int[] _buckets;

        private int _size;

        private int _offset;

        private int _count;

        private int FindEntry(TKey key)
        {
            if (this._size.Equals(0)) return -1;
            var hashCode = this.GetHashCode(key);
            var index = this._buckets[hashCode % this._size];
            while (index > -1)
            {
                if (hashCode == this._entrys[index].HashCode) return index;
                index = this._entrys[index].Next;
            }
            return -1;
        }

        public void Add(TKey key, TValue value)
        {
            this.Insert(key, value);
        }

        private void Insert(TKey key, TValue value)
        {
            this.Expansions();
            var hashCode = this.GetHashCode(key);
            var index = this._buckets[hashCode % this._size];
            while (index > -1)
            {
                if (hashCode == this._entrys[index].HashCode) throw new ArgumentException("this key has been existsed");
                index = this._entrys[index].Next;
            }
            this._entrys[this._offset].Key = key;
            this._entrys[this._offset].Value = value;
            this._entrys[this._offset].HashCode = hashCode;
            if (index > -1) this._entrys[index].Next = this._offset;
            else this._buckets[hashCode % this._size] = this._offset;
            this._offset = this.GetNextIndex(this._offset);
            this._count++;
        }

        private void Expansions()
        {
            if (this._count < this._size - 1) return;
            var newSize = this._size == 0 ? 3 : this._size << 1;
            this.Resize(newSize);
        }

        public void Contraction()
        {
            var size = this._size >> 2;
            if (size < 3 >> 1) return;
            if (this._count >= size) return;
            this._contractionNum++;
            if (this._contractionNum >= 3) this.Resize(this._size >> 1);
        }

        private int _contractionNum;

        private void Resize(int newSize)
        {
            if (newSize <= 0) return;
            var tmpEntrys = this._entrys;
            var tmpSize = this._size;
            this.Initialization(newSize);
            if (null == tmpEntrys) return;
            for (int i = 0; i < tmpSize; i++)
            {
                var tmp = tmpEntrys[i];
                if (CheckNull(tmp)) continue;
                this.Insert(tmp.Key, tmp.Value);
            }
            this._contractionNum = 0;
        }

        private bool CheckNull(Entry entry)
        {
            if (entry.Next == -1 && entry.HashCode == 0) return true;
            return false;
        }

        private void Initialization(int size)
        {
            this._size = size;
            this._offset = 0;
            this._count = 0;
            this._entrys = new Entry[this._size];
            this._buckets = new int[this._size];
            for (int i = 0; i < this._size; i++)
            {
                this._buckets[i] = -1;
                this._entrys[i].Next = -1;
            }
        }

        public bool ContainsKey(TKey key)
        {
            return this.FindEntry(key) > -1;
        }

        public ICollection<TKey> Keys
        {
            get { throw new NotImplementedException(); }
        }

        public bool Remove(TKey key)
        {
            if (null == this._buckets) return false;
            var hashCode = this.GetHashCode(key);
            var index = this._buckets[hashCode % this._size];
            int pre = 0;
            while (index > -1)
            {
                if (hashCode == this._entrys[index].HashCode)
                {
                    if (pre == 0) this._buckets[hashCode % this._size] = this._entrys[index].Next;
                    else this._entrys[pre].Next = this._entrys[index].Next;
                    this.ResetEntry(ref this._entrys[index]);
                    //curr.Next = -1;
                    //curr.HashCode = 0;
                    //curr.Key = default(TKey);
                    //curr.Value = default(TValue);
                    //this._count--;
                    return true;
                }
                pre = index;
                index = this._entrys[index].Next;
            }
            return false;
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            value = default(TValue);
            var index = this.FindEntry(key);
            if (index < 0) return false;
            value = this._entrys[index].Value;
            return true;
        }

        public ICollection<TValue> Values
        {
            get { throw new NotImplementedException(); }
        }

        public TValue this[TKey key]
        {
            get
            {
                TValue obj;
                if (false == this.TryGetValue(key, out obj)) return default(TValue);
                return obj;
            }
            set
            {
                var index = this.FindEntry(key);
                if (index > -1) this._entrys[index].Value = value;
                else this.Add(key, value);
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            throw new NotImplementedException();
        }

        public void Clear()
        {
            for (int i = 0; i < _size; i++) this.ResetEntry(ref this._entrys[i]);
            this._count = 0;
            this._offset = 0;
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return this.ContainsKey(item.Key);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }

        public int Count
        {
            get { throw new NotImplementedException(); }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return this.Remove(item.Key);
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        private int GetHashCode(TKey key)
        {
            return key.GetHashCode() & 2147483647;
        }

        public void Recover(Func<TKey, TValue, bool> recoverFunc)
        {
            for (int i = 0; i < this._size; i++) this._buckets[i] = -1;
            var start = this._offset - 1;
            start = start < 0 ? this._size - 1 : start;
            var curr = start;
            this._count = 0;
            while (true)
            {
                curr = GetNextIndex(curr);
                if (false == this.CheckNull(this._entrys[curr]))
                {
                    var key = this._entrys[curr].Key;
                    var value = this._entrys[curr].Value;
                    this.ResetEntry(ref this._entrys[curr]);
                    if (false == recoverFunc(key, value)) this.Insert(key, value);
                }
                if (curr == start) break;
            }
        }

        private unsafe void ResetEntry(ref Entry entry)
        {
            entry.Key = default(TKey);
            entry.Value = default(TValue);
            entry.HashCode = 0;
            entry.Next = -1;
        }

        private int GetNextIndex(int curr)
        {
            if (curr == this._size - 1) return 0;
            return curr + 1;
        }

#if DEBUG

        internal Tuple<int, int, int> Test()
        {
            return new Tuple<int, int, int>(this._count, this._offset, this._size);
        }

#endif
    }
}